﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Net;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;

namespace NPlurk
{
    /// <summary>
    /// http://www.plurk.com/API
    /// </summary>
    public class PlurkStub : RequestBase
    {
        #region Properties

        private UserChannel _userChannel;
        public UserChannel UserChannel
        {
            get
            {
                if (_userChannel == null)
                {
                    _userChannel = GetUserChannel();
                }
                return _userChannel;
            }
        }

        private string _httpMethod = "GET";

        #endregion

        #region Constructor

        public PlurkStub(string plurkKey) : base(plurkKey)
        {
        }

        #endregion

        #region Users

        /// <summary>
        /// Login an already created user. Login creates a session cookie, which can be used to access the other methods. On success it returns the data returned by /API/Profile/getOwnProfile. 
        /// </summary>
        /// <param name="username"></param>
        /// <param name="password"></param>
        /// <returns></returns>
        public Profile Login(string username, string password)
        {
            var nvc = new NameValueCollection
                  {
                    {"api_key", PlurkKey},
                    {"username", username},
                    {"password", password},
                  };
            return RequestObject<Profile>("https://www.plurk.com/API/Users/login", _httpMethod, nvc);
        }

        /// <summary>
        /// Logout
        /// </summary>
        /// <returns></returns>
        public PlurkError Logout()
        {
            var nvc = ComposeParam(null);
            return RequestObject<PlurkError>("http://www.plurk.com/API/Users/logout", _httpMethod, nvc);
        }

        /// <summary>
        /// Returns info about a user's karma, including current karma, karma growth, karma graph and the latest reason why the karma has dropped.
        /// </summary>
        /// <returns></returns>
        public KarmaStats GetKarmaStats()
        {
            var nvc = new NameValueCollection
                  {
                    {"api_key", PlurkKey},
                  };
            return RequestObject<KarmaStats>("http://www.plurk.com/API/Users/getKarmaStats", _httpMethod, nvc);
        }

        #endregion

        #region Profile

        /// <summary>
        /// Returns data that's private for the currently logged in user. This can be used to construct a profile and render a timeline of the latest plurks.
        /// </summary>
        /// <returns></returns>
        public Profile GetOwnProfile()
        {
            NameValueCollection nvc = ComposeParam(null);
            return RequestObject<Profile>("https://www.plurk.com/API/Profile/getOwnProfile", _httpMethod, nvc);
        }

        /// <summary>
        /// Fetches public information such as a user's public plurks and basic information. Fetches also if the current logged in user is following the user, are friends with or is a fan.
        /// </summary>
        /// <param name="userId"></param>
        /// <returns></returns>
        public Profile GetPublicProfile(long userId)
        {
            NameValueCollection nvc = ComposeParam(null);
            nvc["user_id"] = userId.ToString();
            return RequestObject<Profile>("https://www.plurk.com/API/Profile/getPublicProfile", _httpMethod, nvc);
        }

        /// <summary>
        /// Fetches public information such as a user's public plurks and basic information. Fetches also if the current logged in user is following the user, are friends with or is a fan.
        /// </summary>
        /// <param name="userId"></param>
        /// <returns></returns>
        public Profile GetPublicProfile(string nickName)
        {
            NameValueCollection nvc = ComposeParam(null);
            nvc["user_id"] = nickName;
            return RequestObject<Profile>("https://www.plurk.com/API/Profile/getPublicProfile", _httpMethod, nvc);
        }

        #endregion

        #region Real time notifications

        private UserChannel GetUserChannel()
        {
            NameValueCollection nvc = ComposeParam(null);
            return RequestObject<UserChannel>("https://www.plurk.com/API/Realtime/getUserChannel", _httpMethod, nvc);
        }

        /// <summary>
        /// Your offset is wrong and you need to resync your data: {"new_offset": -3}
        /// </summary>
        /// <returns></returns>
        public UserChannelData ResetRealTimeNotifications()
        {
            return GetRealTimeNotifications(-3);
        }

        /// <summary>
        /// You'll get an URL from /API/Realtime/getUserChannel and you do GET requests to this URL to get new data. 
        /// Your request will sleep for about 50 seconds before returning a response if there is no new data added to your channel. 
        /// You won't get notifications on responses that the logged in user adds, but you will get notifications for new plurks.
        /// </summary>
        /// <returns></returns>
        public UserChannelData GetRealTimeNotifications()
        {
            return GetRealTimeNotifications(0);
        }

        /// <summary>
        /// You'll get an URL from /API/Realtime/getUserChannel and you do GET requests to this URL to get new data. 
        /// Your request will sleep for about 50 seconds before returning a response if there is no new data added to your channel. 
        /// You won't get notifications on responses that the logged in user adds, but you will get notifications for new plurks.
        /// </summary>
        /// <param name="offset">Only fetch new messages from a given offset. You'll get offset when a response is returned, it's returned as new_offset.</param>
        /// <returns></returns>
        private UserChannelData GetRealTimeNotifications(int offset)
        {
            NameValueCollection nvc = ComposeParam(new { offset = offset.ToString() });
            var url = Regex.Replace(UserChannel.comet_server, @"[\?&]offset=\-?\d+", "");

            var response = Request(url, _httpMethod, nvc);
            response = Regex.Replace(response, @"^CometChannel.scriptCallback\(|\);$", "");
            var channelData = _jss.Deserialize<UserChannelData>(response);
            return channelData;
        }

        #endregion

        #region Polling

        /// <summary>
        /// You should use this call to find out if there any new plurks posted to the user's timeline. 
        /// It's much more efficient than doing it with /API/Timeline/getPlurks, so please use it :)
        /// </summary>
        /// <param name="newerThan"></param>
        /// <returns></returns>
        public Plurks PollingPlurks(DateTime newerThan)
        {
            //NameValueCollection nvc = ComposeParam(new { offset = newerThan.ToUniversalTime().ToString("s") });
            NameValueCollection nvc = ComposeParam(new { offset = newerThan.ToString("s") });

            var plurks = RequestObject<Plurks>("https://www.plurk.com/API/Polling/getPlurks", _httpMethod, nvc);
            return plurks;
        }

        /// <summary>
        /// Use this call to find out if there are unread plurks on a user's timeline.
        /// </summary>
        /// <returns></returns>
        public UnreadStats GetUnreadCount()
        {
            NameValueCollection nvc = ComposeParam(null);

            var unreadStats = RequestObject<UnreadStats>("https://www.plurk.com/API/Polling/getUnreadCount", _httpMethod, nvc);
            return unreadStats;
        }

        #endregion

        #region Timeline

        public UserPlurk GetPlurk(long plurkId)
        {
            return GetPlurk(plurkId.ToString());
        }

        public UserPlurk GetPlurk(string plurkId)
        {
            var nvc = new NameValueCollection
                  {
                    {"api_key", PlurkKey},
                    {"plurk_id", plurkId}
                  };
            return RequestObject<UserPlurk>("https://www.plurk.com/API/Timeline/getPlurk", _httpMethod, nvc);
        }

        public Plurks GetPlurks()
        {
            return GetPlurks(null);
        }

        public Plurks GetPlurks(object param)
        {
            NameValueCollection nvc = ComposeParam(param);
            return RequestObject<Plurks>("https://www.plurk.com/API/Timeline/getPlurks", _httpMethod, nvc);
        }

        public Plurks GetUnreadPlurks()
        {
            return GetUnreadPlurks(null);
        }

        public Plurks GetUnreadPlurks(object param)
        {
            NameValueCollection nvc = ComposeParam(param);
            return RequestObject<Plurks>("https://www.plurk.com/API/Timeline/getUnreadPlurks", _httpMethod, nvc);
        }

        public PlurkResult MarkAsRead(long[] plurkIds)
        {
            return MarkAsRead(Array.ConvertAll(plurkIds, Convert.ToString));
        }

        public PlurkResult MarkAsRead(string[] plurkIds)
        {
            NameValueCollection nvc = ComposeParam(null);
            var sb = new StringBuilder();
            string seprator = "";
            sb.Append("[");
            foreach (string plurkId in plurkIds)
            {
                sb.Append(seprator).Append(plurkId);
                seprator = ",";
            }
            sb.Append("]");
            nvc["plurk_id"] = sb.ToString();
            return RequestObject<PlurkResult>("https://www.plurk.com/API/Timeline/markAsRead", _httpMethod, nvc);
        }

        public Plurk AddPlurk(string content)
        {
            return AddPlurk("says", content);
        }

        public Plurk AddPlurk(string qualifier, string content)
        {
            var nvc = new NameValueCollection
                  {
                    {"api_key", PlurkKey},
                    {"qualifier", qualifier},
                    {"content", content},
                    {"lang", Lang}
                  };
            return RequestObject<Plurk>("https://www.plurk.com/API/Timeline/plurkAdd", _httpMethod, nvc);
        }

        public PlurkResult DeletePlurk(long plurkId)
        {
            return DeletePlurk(plurkId.ToString());
        }

        public PlurkResult DeletePlurk(string plurkId)
        {
            NameValueCollection nvc = ComposeParam(null);
            nvc["plurk_id"] = plurkId;
            return RequestObject<PlurkResult>("https://www.plurk.com/API/Timeline/plurkDelete", _httpMethod, nvc);
        }

        public Plurk EditPlurk(long plurkId, string content)
        {
            return EditPlurk(plurkId.ToString(), content);
        }

        public Plurk EditPlurk(string plurkId, string content)
        {
            NameValueCollection nvc = ComposeParam(null);
            nvc["plurk_id"] = plurkId;
            nvc["content"] = content;
            return RequestObject<Plurk>("https://www.plurk.com/API/Timeline/plurkEdit", _httpMethod, nvc);
        }

        #endregion

        #region Response

        /// <summary>
        /// Fetches responses for plurk with plurk_id and some basic info about the users.
        /// </summary>
        /// <param name="plurkId"></param>
        /// <returns></returns>
        public Responses GetResponse(long plurkId)
        {
            return GetResponse(plurkId.ToString());
        }

        public Responses GetResponse(string plurkId)
        {
            NameValueCollection nvc = ComposeParam(null);
            nvc["plurk_id"] = plurkId;
            nvc["from_response"] = "0";
            return RequestObject<Responses>("https://www.plurk.com/API/Responses/get", _httpMethod, nvc);
        }

        /// <summary>
        /// Adds a responses to plurk_id. Language is inherited from the plurk.
        /// </summary>
        /// <param name="plurkId"></param>
        /// <param name="content"></param>
        /// <returns></returns>
        public Plurk AddResponse(long plurkId, string content)
        {
            return AddResponse(plurkId.ToString(), content);
        }

        public Plurk AddResponse(string plurkId, string content)
        {
            return AddResponse(plurkId, "says", content);
        }

        /// <summary>
        /// Adds a responses to plurk_id. Language is inherited from the plurk.
        /// </summary>
        /// <param name="plurkId"></param>
        /// <param name="qualifier"></param>
        /// <param name="content"></param>
        /// <returns></returns>
        public Plurk AddResponse(long plurkId, string qualifier, string content)
        {
            return AddResponse(plurkId.ToString(), qualifier, content);
        }

        public Plurk AddResponse(string plurkId, string qualifier, string content)
        {
            var nvc = ComposeParam(new { plurk_id = plurkId, qualifier = qualifier, content = content });
            return RequestObject<Plurk>("https://www.plurk.com/API/Responses/responseAdd", _httpMethod, nvc);
        }

        /// <summary>
        /// Deletes a response. A user can delete own responses or responses that are posted to own plurks.
        /// </summary>
        /// <param name="plurkId"></param>
        /// <param name="qualifier"></param>
        /// <param name="content"></param>
        /// <returns></returns>
        public PlurkResult DeleteResponse(long plurkId, int responseId)
        {
            return DeleteResponse(plurkId.ToString(), responseId.ToString());
        }

        public PlurkResult DeleteResponse(string plurkId, string responseId)
        {
            NameValueCollection nvc = ComposeParam(new { plurk_id = plurkId, response_id = responseId });
            return RequestObject<PlurkResult>("https://www.plurk.com/API/Responses/responseDelete", _httpMethod, nvc);
        }

        #endregion

        #region Friends and fans

        /// <summary>
        /// Returns user_id's friend list into existed IList<User> container at a time.
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="friends"></param>
        /// <returns></returns>
        public int GetFriends(long userId, IList<User> friends)
        {
            int offset = 0;

            while (true)
            {
                User[] pagedUsers = GetFriendsByOffset(userId, offset);
                foreach (User user in pagedUsers)
                {
                    friends.Add(user);
                }
                if (pagedUsers.Length < 10)
                {
                    break;
                }
                offset += 10;
            }
            return friends.Count;
        }

        /// <summary>
        /// Returns user_id's friend one by one.
        /// </summary>
        /// <param name="userId"></param>
        /// <returns></returns>
        public IEnumerable<User> GetFriends(long userId)
        {
            int offset = 0;

            while (true)
            {
                User[] pagedUsers = GetFriendsByOffset(userId, offset);
                foreach (User user in pagedUsers)
                {
                    yield return user;
                }
                if (pagedUsers.Length < 10)
                {
                    break;
                }
                offset += 10;
            }
        }

        /// <summary>
        /// Returns user_id's friend list in chucks of 10 friends at a time.
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="offset"></param>
        /// <returns></returns>
        protected User[] GetFriendsByOffset(long userId, int offset)
        {
            NameValueCollection nvc = ComposeParam(null);
            nvc["user_id"] = userId.ToString();
            if (offset > 0)
            {
                nvc["offset"] = offset.ToString();
            }
            return RequestObject<User[]>("https://www.plurk.com/API/FriendsFans/getFriendsByOffset", _httpMethod, nvc);
        }

        /// <summary>
        /// Returns user_id's fans list into IList<User> at a time.
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="fans"></param>
        /// <returns></returns>
        public int GetFans(long userId, IList<User> fans)
        {
            int offset = 0;

            while (true)
            {
                User[] pagedUsers = GetFansByOffset(userId, offset);
                foreach (User user in pagedUsers)
                {
                    fans.Add(user);
                }
                if (pagedUsers.Length < 10)
                {
                    break;
                }
                offset += 10;
            }
            return fans.Count;
        }

        /// <summary>
        /// Returns user_id's fans one by one.
        /// </summary>
        /// <param name="userId"></param>
        /// <returns></returns>
        public IEnumerable<User> GetFans(long userId)
        {
            int offset = 0;

            while (true)
            {
                User[] pagedUsers = GetFansByOffset(userId, offset);
                foreach (User user in pagedUsers)
                {
                    yield return user;
                }
                if (pagedUsers.Length < 10)
                {
                    break;
                }
                offset += 10;
            }
        }

        /// <summary>
        /// Returns user_id's fans list in chucks of 10 fans at a time.
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="offset"></param>
        /// <returns></returns>
        protected User[] GetFansByOffset(long userId, int offset)
        {
            NameValueCollection nvc = ComposeParam(null);
            nvc["user_id"] = userId.ToString();
            if (offset > 0)
            {
                nvc["offset"] = offset.ToString();
            }
            return RequestObject<User[]>("https://www.plurk.com/API/FriendsFans/getFansByOffset", _httpMethod, nvc);
        }

        /// <summary>
        /// Returns users that the current logged in user follows as fan - in chucks of 10 fans at a time.
        /// </summary>
        /// <param name="offset"></param>
        /// <returns></returns>
        public User[] GetFollowingByOffset(long offset)
        {
            NameValueCollection nvc = ComposeParam(null);
            if (offset > 0)
            {
                nvc["offset"] = offset.ToString();
            }
            return RequestObject<User[]>("https://www.plurk.com/API/FriendsFans/getFollowing", _httpMethod, nvc);
        }

        /// <summary>
        /// Create a friend request to friend_id. User with friend_id has to accept a friendship.
        /// </summary>
        /// <param name="friendId"></param>
        /// <returns></returns>
        public PlurkResult BecomeFriend(long friendId)
        {
            NameValueCollection nvc = ComposeParam(null);
            nvc["friend_id"] = friendId.ToString();
            return RequestObject<PlurkResult>("https://www.plurk.com/API/FriendsFans/becomeFriend", _httpMethod, nvc);
        }

        /// <summary>
        /// Remove friend with ID friend_id. friend_id won't be notified.
        /// </summary>
        /// <param name="friendId"></param>
        /// <returns></returns>
        public PlurkResult RemoveAsFriend(long friendId)
        {
            NameValueCollection nvc = ComposeParam(null);
            nvc["friend_id"] = friendId.ToString();
            return RequestObject<PlurkResult>("https://www.plurk.com/API/FriendsFans/removeAsFriend", _httpMethod, nvc);
        }

        /// <summary>
        /// Become fan of fan_id. To stop being a fan of someone, user /API/FriendsFans/setFollowing?fan_id=FAN_ID&follow=false.
        /// </summary>
        /// <param name="fanId"></param>
        /// <returns></returns>
        public PlurkResult BecomeFan(long fanId)
        {
            NameValueCollection nvc = ComposeParam(null);
            nvc["fan_id"] = fanId.ToString();
            return RequestObject<PlurkResult>("https://www.plurk.com/API/FriendsFans/becomeFan", _httpMethod, nvc);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="fanId"></param>
        /// <returns></returns>
        //public PlurkResult RemoveAsFan(long fanId)
        //{
        //    NameValueCollection nvc = ComposeParam(null);
        //    nvc["fan_id"] = fanId.ToString();
        //    return RequestObject<PlurkResult>("https://www.plurk.com/API/FriendsFans/removeAsFan", _httpMethod, nvc);
        //}

        /// <summary>
        /// Update following of user_id. A user can befriend someone, but can unfollow them. This request is also used to stop following someone as a fan.
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="follow"></param>
        /// <returns></returns>
        public PlurkResult SetFollowing(long userId, bool follow)
        {
            NameValueCollection nvc = ComposeParam(null);
            nvc["user_id"] = userId.ToString();
            nvc["follow"] = follow.ToString();
            return RequestObject<PlurkResult>("https://www.plurk.com/API/FriendsFans/setFollowing", _httpMethod, nvc);
        }

        /// <summary>
        /// Returns logged in users friends (nick name and full name). This information can be used to construct auto-completion for private plurking. Notice that a friend list can be big, depending on how many friends a user has, so this list should be lazy-loaded in your application.
        /// </summary>
        /// <returns></returns>
        public Dictionary<string, ConciseUser> GetCompletion()
        {
            NameValueCollection nvc = ComposeParam(null);
            return RequestObject<Dictionary<string, ConciseUser>>("https://www.plurk.com/API/FriendsFans/getCompletion", _httpMethod, nvc);
        }

        #endregion

        #region Search

        /// <summary>
        /// Returns the enumerable plurks on a search term.
        /// </summary>
        /// <param name="text"></param>
        /// <param name="offset"></param>
        /// <returns></returns>
        public IEnumerable<UserPlurk> Search(string text, int? offset = null)
        {
            while (true)
            {
                SearchResult result = _Search(text, offset);
                foreach (var plurk in result.plurks)
                {
                    yield return new UserPlurk() 
                    { 
                        user = result.users[plurk.user_id.ToString()], 
                        plurk = plurk 
                    };
                }
                if (!result.has_more || result.plurks.Length == 0)
                {
                    break;
                }
                if (offset.HasValue)
                {
                    offset += 20;
                }
                else
                {
                    offset = 20;
                }
            }
        }

        /// <summary>
        /// Returns the latest 20 plurks on a search term.
        /// </summary>
        /// <param name="text"></param>
        /// <param name="offset"></param>
        /// <returns></returns>
        protected SearchResult _Search(string text, int? offset = null)
        {
            NameValueCollection nvc = ComposeParam(null);
            nvc["query"] = text;
            if (offset.HasValue)
            {
                nvc["offset"] = offset.Value.ToString();
            }
            return RequestObject<SearchResult>("https://www.plurk.com/API/PlurkSearch/search", _httpMethod, nvc);
        }

        /// <summary>
        /// Returns enumerable users that match query, users are sorted by karma.
        /// </summary>
        /// <param name="text"></param>
        /// <param name="offset"></param>
        /// <returns></returns>
        public IEnumerable<User> SearchUser(string text, int? offset = null)
        {
            while (true)
            {
                SearchUserResult result = _SearchUser(text, offset);
                foreach (User user in result.users)
                {
                    yield return user;
                }
                if (result.users.Length == 0)
                {
                    break;
                }
                if (offset.HasValue)
                {
                    offset += 10;
                }
                else
                {
                    offset = 10;
                }
            }
        }

        /// <summary>
        /// Returns 10 users that match query, users are sorted by karma.
        /// </summary>
        /// <param name="text"></param>
        /// <param name="offset"></param>
        /// <returns></returns>
        protected SearchUserResult _SearchUser(string text, int? offset = null)
        {
            NameValueCollection nvc = ComposeParam(null);
            nvc["query"] = text;
            if (offset.HasValue)
            {
                nvc["offset"] = offset.Value.ToString();
            }
            return RequestObject<SearchUserResult>("https://www.plurk.com/API/UserSearch/search", _httpMethod, nvc);
        }

        #endregion

        #region Emoticons

        /// <summary>
        /// Emoticons are a big part of Plurk since they make it easy to express feelings. 
        /// Check out current Plurk emoticons. 
        /// http://www.plurk.com/Help/extraSmilies
        /// </summary>
        /// <returns></returns>
        public Emoticons GetEmoticons()
        {
            NameValueCollection nvc = ComposeParam(null);
            return RequestObject<Emoticons>("https://www.plurk.com/API/Emoticons/get", _httpMethod, nvc);
        }

        #endregion

        #region Cliques

        /// <summary>
        /// Returns a list of users current cliques.
        /// </summary>
        /// <returns></returns>
        public string[] GetCliques()
        {
            NameValueCollection nvc = ComposeParam(null);
            return RequestObject<string[]>("https://www.plurk.com/API/Cliques/getCliques", _httpMethod, nvc);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="cliqueName"></param>
        /// <returns></returns>
        public PlurkResult CreateClique(string cliqueName)
        {
            NameValueCollection nvc = ComposeParam(null);
            nvc["clique_name"] = cliqueName;
            return RequestObject<PlurkResult>("https://www.plurk.com/API/Cliques/createClique", _httpMethod, nvc);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="cliqueName"></param>
        /// <param name="newName"></param>
        /// <returns></returns>
        public PlurkResult RenameClique(string cliqueName, string newName)
        {
            NameValueCollection nvc = ComposeParam(null);
            nvc["clique_name"] = cliqueName;
            nvc["new_name"] = newName;
            return RequestObject<PlurkResult>("https://www.plurk.com/API/Cliques/renameClique", _httpMethod, nvc);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="cliqueName"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        public PlurkResult AddClique(string cliqueName, int userId)
        {
            NameValueCollection nvc = ComposeParam(null);
            nvc["clique_name"] = cliqueName;
            nvc["user_id"] = userId.ToString();
            return RequestObject<PlurkResult>("https://www.plurk.com/API/Cliques/add", _httpMethod, nvc);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="cliqueName"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        public PlurkResult RemoveClique(string cliqueName, int userId)
        {
            NameValueCollection nvc = ComposeParam(null);
            nvc["clique_name"] = cliqueName;
            nvc["user_id"] = userId.ToString();
            return RequestObject<PlurkResult>("https://www.plurk.com/API/Cliques/remove", _httpMethod, nvc);
        }

        #endregion
    }
}